package org.chipu.jnids.sce.profile;

import static org.chipu.jnids.sce.profile.StatType.Type.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.chipu.shared.util.singletons.NetworkUtils;
import org.chipu.shared.util.singletons.Utils;
import org.w3c.dom.*;

public final class StatType implements Comparable<StatType> {
	public enum Type {
		CONNECTIONS_IN, CONNECTIONS_OUT, CONNECTIONS_IN_BY_PROTOCOL, CONNECTIONS_OUT_BY_PROTOCOL,
		/* These types refers to single hosts */
		CONNECTIONS_TO_INTERNAL, CONNECTIONS_FROM_EXTERNAL, CONNECTIONS_FROM_INTERNAL,
		CONNECTIONS_TO_EXTERNAL
		/*
		 * , DATA_IN, DATA_OUT, DATA_IN_BY_PROTOCOL, DATA_OUT_BY_PROTOCOL
		 */;
		public boolean dependsOnProtocol() {
			switch(this) {
			case CONNECTIONS_IN_BY_PROTOCOL:
			case CONNECTIONS_OUT_BY_PROTOCOL:
				return true;
			default:
				return false;
			}
		}
	}
	@Nonnull
	public final Type type;
	@Nullable
	public final InetAddress address;
	private final byte protocol;
	private final static Map<Type, StatType> preloadedInstances = new HashMap<Type, StatType>(8);// 6/0.75=8
	static {
		Type[] array = new Type[]{CONNECTIONS_IN, CONNECTIONS_OUT, CONNECTIONS_TO_INTERNAL,
			CONNECTIONS_FROM_INTERNAL, CONNECTIONS_FROM_EXTERNAL, CONNECTIONS_TO_EXTERNAL};
		for(Type t : array)
			preloadedInstances.put(t, new StatType(t));
	}

	private StatType(Type type) {
		this(type, null, (byte) 0);
	}

	public StatType(Type type, byte protocol) {
		this(type, null, protocol);
	}

	public StatType(Type type, InetAddress address) {
		this(type, address, (byte) 0);
	}

	private StatType(Type type, @Nullable InetAddress address, byte protocol) {
		if (type.dependsOnProtocol() && protocol == 0)
			throw new IllegalArgumentException("Invalid protocol for Type=" + type);
		if (protocol < 0)
			throw new IllegalArgumentException("Invalid protocol: " + protocol);
		this.type = type;
		this.address = address;
		this.protocol = protocol;
	}

	public static StatType getStatType(Type type) {
		return preloadedInstances.get(type);
	}

	public String encode() {
		return "<stattype type=\"" + type + (address == null? "": "\" address=\"" + address.getHostAddress())
			+ "\" protocol=\"" + protocol + "\" />";
	}

	public Element encode(Document rootDoc) {
		Element e = rootDoc.createElement("stattype");
		e.setAttribute("type", type.toString());
		if (address != null)
			e.setAttribute("address", address.getHostAddress());
		e.setAttribute("protocol", String.valueOf(protocol));
		return e;
	}

	public static StatType decode(String s) throws UnknownHostException {
		assert s.startsWith("<stattype ");
		String host = getValue(s, "address");
		return new StatType(Type.valueOf(getValue(s, "type")), Utils.isNullOrEmpty(host)? null: InetAddress
			.getByName(host), Byte.parseByte(getValue(s, "protocol")));
	}

	public static StatType decode(Node n) throws UnknownHostException {
		NamedNodeMap attb = n.getAttributes();
		String host = attb.getNamedItem("address").getNodeValue();
		return new StatType(Type.valueOf(attb.getNamedItem("type").getNodeValue()),
			Utils.isNullOrEmpty(host)? null: InetAddress.getByName(host), Byte.parseByte(attb.getNamedItem(
				"protocol").getNodeValue()));
	}

	@Override
	public int hashCode() {
		int ipHash = address == null? 0: address.hashCode();
		return ipHash * 10 + type.ordinal();
	}

	@Override
	public String toString() {
		return type + (address == null? "": ": " + address)
			+ (type.dependsOnProtocol()? ": " + NetworkUtils.guessProtocolName(protocol): "");
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null || !obj.getClass().isAssignableFrom(getClass()))
			return false;
		StatType o = (StatType) obj;
		return type == o.type && (address == o.address || address != null && address.equals(o.address))
			&& protocol == o.protocol;
	}

	@Override
	public int compareTo(StatType o) {
		int rta = type.ordinal() - o.type.ordinal();
		if (rta != 0)
			return rta;
		rta = protocol - o.protocol;
		if (rta != 0)
			return rta;
		if (address == o.address)
			return 0;
		if (address == null)
			return -1;
		if (o.address == null)
			return 1;
		return address.getHostAddress().compareTo(o.address.getHostAddress());
	}

	// -----

	public static int getIntValue(String text, String name) {
		return Integer.parseInt(getValue(text, name));
	}

	/** @return the first not commented value for that parameter name */
	@Nullable
	public static String getValue(String text, String name) {
		int i = 0;
		while(true) {
			i = text.indexOf(name + "=", i + 1);
			if (i == -1)
				return null;
			int startCommentIndex = text.lastIndexOf("<!--", i);
			int endCommentIndex = text.lastIndexOf("-->", i);
			if (startCommentIndex <= endCommentIndex)
				break;
			i++;
		}
		int ii = i + name.length() + 2;
		return text.substring(ii, text.indexOf("\"", ii));
	}
}